package com.xm.bluetoothkotlin

import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.os.Looper
import android.provider.Settings
import com.blankj.utilcode.util.*
import com.xm.bluetoothkotlin.connect.BleBluetooth
import com.xm.bluetoothkotlin.connect.ConnectConfigData
import com.xm.bluetoothkotlin.connect.SplitWriter
import com.xm.bluetoothkotlin.connect.back.*
import com.xm.bluetoothkotlin.constant.DEFAULT_WRITE_DATA_SPLIT_COUNT
import com.xm.bluetoothkotlin.device.BleConnectData
import com.xm.bluetoothkotlin.device.BluetoothDeviceData
import com.xm.bluetoothkotlin.receiver.BluetoothReceiver
import com.xm.bluetoothkotlin.receiver.ReceiverCallBack
import com.xm.bluetoothkotlin.search.BluetoothSeachCallback
import com.xm.bluetoothkotlin.search.BluetoothSearch
import com.xm.bluetoothkotlin.search.BluetoothSearchFilter
import com.xm.bluetoothkotlin.service.BluetoothService
import com.xm.bluetoothkotlin.util.PermissionHelp

/**
 * @创建者:小垚
 * @时间:2021/11/11
 *@描述: 蓝牙工具工厂
 */
@SuppressLint("StaticFieldLeak")
object BluetoothManager {
    // 广播监听
    var receivers = mutableListOf<ReceiverCallBack>()

    //搜索监听
    var searchs = mutableListOf<BluetoothSeachCallback>()

    // 设备监听
    var devices = mutableListOf<BluetoothConnectCallback>()

    // 已连接设备
    var connectDevices = mutableMapOf<String, BleConnectData>()

    // 已搜索到的设备
    var searchDevices = mutableListOf<BluetoothDeviceData>()

    // 搜索蓝牙过滤
    var searchFilter = BluetoothSearchFilter()

    //监听蓝牙广播
    private val receiver by lazy { BluetoothReceiver() }

    //连接配置
    var mConfig = ConnectConfigData()

    //上下文
    lateinit var context: Context

    //蓝牙工具
    lateinit var bluetoothManager: BluetoothManager
    lateinit var bluetoothAdapter: BluetoothAdapter

    /**
     * 初始化
     */
    fun init(context: Context) {
        this.context = context
        bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        bluetoothAdapter = bluetoothManager.adapter
    }


    /**
     * notify
     */
    fun notify(
        bleDevice: BluetoothDeviceData,
        callback: BleNotifyCallback?
    ) {
        notify(bleDevice, mConfig, false, callback)
    }

    /**
     * notify
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_notify
     * @param useCharacteristicDescriptor
     * @param callback
     */
    fun notify(
        bleDevice: BluetoothDeviceData,
        config: ConnectConfigData,
        useCharacteristicDescriptor: Boolean,
        callback: BleNotifyCallback?
    ) {
        requireNotNull(callback) { "BleNotifyCallback can not be Null!" }
        val bleBluetooth = getConnectBleBluetooth(bleDevice)
        if (bleBluetooth == null) {
            callback.onNotifyFailure("This device not connect!")
        } else {
            bleBluetooth.newBleConnector()
                .withUUIDString(config.service_uuid, config.notify_characteristic_uuid)
                .enableCharacteristicNotify(callback, config.notify_characteristic_uuid, useCharacteristicDescriptor)
        }
    }

    /**
     * indicate
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_indicate
     * @param callback
     */
    fun indicate(
        bleDevice: BluetoothDeviceData,
        callback: BleIndicateCallback?
    ) {
        indicate(bleDevice, mConfig, false, callback)
    }

    /**
     * indicate
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_indicate
     * @param useCharacteristicDescriptor
     * @param callback
     */
    fun indicate(
        bleDevice: BluetoothDeviceData,
        config: ConnectConfigData,
        useCharacteristicDescriptor: Boolean,
        callback: BleIndicateCallback?
    ) {
        requireNotNull(callback) { "BleIndicateCallback can not be Null!" }
        val bleBluetooth = getConnectBleBluetooth(bleDevice)
        if (bleBluetooth == null) {
            callback.onIndicateFailure("This device not connect!")
        } else {
            bleBluetooth.newBleConnector()
                .withUUIDString(config.service_uuid, config.indicate_characteristic_uuid)
                .enableCharacteristicIndicate(callback, config.indicate_characteristic_uuid, useCharacteristicDescriptor)
        }
    }

    /**
     * stop notify, remove callback
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_notify
     * @return
     */
    fun stopNotify(
        bleDevice: BluetoothDeviceData,
    ): Boolean {
        return stopNotify(bleDevice, mConfig, false)
    }

    /**
     * stop notify, remove callback
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_notify
     * @param useCharacteristicDescriptor
     * @return
     */
    fun stopNotify(
        bleDevice: BluetoothDeviceData,
        config: ConnectConfigData,
        useCharacteristicDescriptor: Boolean
    ): Boolean {
        val bleBluetooth = getConnectBleBluetooth(bleDevice) ?: return false


        val success: Boolean = bleBluetooth.newBleConnector()
            .withUUIDString(config.service_uuid, config.notify_characteristic_uuid)
            .disableCharacteristicNotify(useCharacteristicDescriptor)
        if (success) {
            bleBluetooth.removeNotifyCallback(config.notify_characteristic_uuid)
        }
        return success
    }

    /**
     * stop indicate, remove callback
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_indicate
     * @return
     */
    fun stopIndicate(
        bleDevice: BluetoothDeviceData,
    ): Boolean {
        return stopIndicate(bleDevice, mConfig, false)
    }

    /**
     * stop indicate, remove callback
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_indicate
     * @param useCharacteristicDescriptor
     * @return
     */
    fun stopIndicate(
        bleDevice: BluetoothDeviceData,
        config: ConnectConfigData,
        useCharacteristicDescriptor: Boolean
    ): Boolean {
        val bleBluetooth = getConnectBleBluetooth(bleDevice) ?: return false
        val success: Boolean = bleBluetooth.newBleConnector()
            .withUUIDString(config.service_uuid, config.indicate_characteristic_uuid)
            .disableCharacteristicIndicate(useCharacteristicDescriptor)
        if (success) {
            bleBluetooth.removeIndicateCallback(config.indicate_characteristic_uuid)
        }
        return success
    }

    /**
     * write
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_write
     * @param data
     * @param callback
     */
    fun write(
        bleDevice: BluetoothDeviceData,
        data: ByteArray,
        callback: BleWriteCallback
    ) {
        write(bleDevice, data, true, callback)
    }

    /**
     * write
     *
     * @param bleDevice
     * @param data
     * @param split
     * @param callback
     */
    fun write(
        bleDevice: BluetoothDeviceData,
        data: ByteArray,
        split: Boolean,
        callback: BleWriteCallback
    ) {
        write(bleDevice, data, split, true, 0, callback)
    }

    /**
     * write
     *
     * @param bleDevice
     * @param data
     * @param split
     * @param sendNextWhenLastSuccess
     * @param intervalBetweenTwoPackage
     * @param callback
     */
    fun write(
        bleDevice: BluetoothDeviceData,
        data: ByteArray,
        split: Boolean,
        sendNextWhenLastSuccess: Boolean,
        intervalBetweenTwoPackage: Long,
        callback: BleWriteCallback?
    ) {
        requireNotNull(callback) { "BleWriteCallback can not be Null!" }

        if (data.size > 20 && !split) {
            LogUtils.w("Be careful: data's length beyond 20! Ensure MTU higher than 23, or use spilt write!")
        }
        val bleBluetooth = getConnectBleBluetooth(bleDevice)
        if (bleBluetooth == null) {
            callback.onWriteFailure("This device not connect!")
        } else {
            if (split && data.size > DEFAULT_WRITE_DATA_SPLIT_COUNT) {
                SplitWriter().splitWrite(
                    bleBluetooth, bleBluetooth.config!!.service_uuid, bleBluetooth.config!!.write_characteristic_uuid, data,
                    sendNextWhenLastSuccess, intervalBetweenTwoPackage, callback
                )
            } else {
                bleBluetooth.newBleConnector()
                    .withUUIDString(bleBluetooth.config!!.service_uuid, bleBluetooth.config!!.write_characteristic_uuid)
                    .writeCharacteristic(data, callback, bleBluetooth.config!!.write_characteristic_uuid)
            }
        }
    }

    /**
     * read
     *
     * @param bleDevice
     * @param uuid_service
     * @param uuid_read
     * @param callback
     */
    fun read(
        bleDevice: BluetoothDeviceData,
        uuid_service: String,
        uuid_read: String,
        callback: BleReadCallback?
    ) {
        requireNotNull(callback) { "BleReadCallback can not be Null!" }
        val bleBluetooth = getConnectBleBluetooth(bleDevice)
        if (bleBluetooth == null) {
            callback.onReadFailure("This device is not connected!")
        } else {
            bleBluetooth.newBleConnector()
                .withUUIDString(uuid_service, uuid_read)
                .readCharacteristic(callback, uuid_read)
        }
    }

    /**
     * read Rssi
     *
     * @param bleDevice
     * @param callback
     */
    fun readRssi(
        bleDevice: BluetoothDeviceData,
        callback: BleRssiCallback?
    ) {
        requireNotNull(callback) { "BleRssiCallback can not be Null!" }
        val bleBluetooth = getConnectBleBluetooth(bleDevice)
        if (bleBluetooth == null) {
            callback.onRssiFailure("This device is not connected!")
        } else {
            bleBluetooth.newBleConnector().readRemoteRssi(callback)
        }
    }

    /**
     * set Mtu
     *
     * @param bleDevice
     * @param mtu
     * @param callback
     */
    fun setMtu(
        bleDevice: BluetoothDeviceData,
        mtu: Int,
        callback: BleMtuCallback
    ) {
        requireNotNull(callback) { "BleMtuChangedCallback can not be Null!" }
        if (mtu > 512) {
            LogUtils.e("requiredMtu should lower than 512 !")
            callback.onSetMTUFailure("requiredMtu should lower than 512 !")
            return
        }
        if (mtu < 23) {
            LogUtils.e("requiredMtu should higher than 23 !")
            callback.onSetMTUFailure("requiredMtu should higher than 23 !")
            return
        }
        val bleBluetooth = getConnectBleBluetooth(bleDevice)
        if (bleBluetooth == null) {
            callback.onSetMTUFailure("This device is not connected!")
        } else {
            bleBluetooth.newBleConnector().setMtu(mtu, callback)
        }
    }

    /**
     * requestConnectionPriority
     *
     * @param connectionPriority Request a specific connection priority. Must be one of
     * [BluetoothGatt.CONNECTION_PRIORITY_BALANCED],
     * [BluetoothGatt.CONNECTION_PRIORITY_HIGH]
     * or [BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER].
     * @throws IllegalArgumentException If the parameters are outside of their
     * specified range.
     */
    fun requestConnectionPriority(bleDevice: BluetoothDeviceData, connectionPriority: Int): Boolean {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val bleBluetooth = getConnectBleBluetooth(bleDevice)
            return bleBluetooth?.newBleConnector()?.requestConnectionPriority(connectionPriority) ?: false
        }
        return false
    }


    /**
     * 连接设备
     */
    fun connect(mac: String, callback: BluetoothConnectCallback): BluetoothDeviceData {
        var name = SPStaticUtils.getString(mac)
        val deviceData = BluetoothDeviceData(null, mac, name, 0)
        connect(deviceData, mConfig, callback)
        return deviceData
    }

    /**
     * 连接设备
     */
    fun connect(mac: String, config: ConnectConfigData, callback: BluetoothConnectCallback): BluetoothDeviceData {
        var name = SPStaticUtils.getString(mac)
        val deviceData = BluetoothDeviceData(null, mac, name, 0)
        connect(deviceData, config, callback)
        return deviceData
    }

    /**
     * 连接设别
     */
    fun connect(deviceData: BluetoothDeviceData, config: ConnectConfigData, callback: BluetoothConnectCallback) {
        if (isBlueEnable()) {
            if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) {
                LogUtils.e("不是在主线程中")
            }
            val filter = connectDevices.keys.filter { deviceData.address == it }
            if (filter.isNotEmpty()) {
                connectDevices[deviceData.address]!!.connect.connect(config, callback)
            } else {
                BleBluetooth(deviceData).connect(config, callback)
            }
        } else {
            enableBluetooth()
        }
    }

    fun disconnect(mac: String) {
        var name = SPStaticUtils.getString(mac)
        disconnect(BluetoothDeviceData(null, mac, name, 0))
    }

    fun disconnect(deviceData: BluetoothDeviceData) {
        if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) {
            LogUtils.e("不是在主线程中")
        }
        val filter = connectDevices.keys.filter { deviceData.address == it }
        if (filter.isNotEmpty()) {
            connectDevices[deviceData.address]!!.connect.disconnect()
        } else {
            BleBluetooth(deviceData).disconnect()
        }
    }

    /**
     * 新增连接设别
     */
    fun addConnecting(bleBluetooth: BleBluetooth) {
        if (!connectDevices.containsKey(bleBluetooth.device.address)) {
            var name = bleBluetooth.device.name
            connectDevices[bleBluetooth.device.address] = BleConnectData(bleBluetooth, name)
        }
    }

    /**
     * 移除连接设别
     */
    fun removeConnecting(bleBluetooth: BleBluetooth) {
        if (connectDevices.containsKey(bleBluetooth.device.address)) {
            connectDevices.remove(bleBluetooth.device.address)
        }
    }


    /**
     * 新增设备监听
     */
    fun addConnectCallback(callback: BluetoothConnectCallback) {
        if (!devices.contains(callback)) {
            devices.add(callback)
        }
    }

    /**
     * 新增设备监听
     */
    fun removeConnectCallback(callback: BluetoothConnectCallback) {
        if (devices.contains(callback)) {
            devices.remove(callback)
        }
    }


    /**
     * 是否支持低功耗蓝牙
     *
     * @return
     */
    fun isSupportBle(): Boolean {
        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 &&
                context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))
    }


    /**
     * 搜索蓝牙
     */
    fun search() {
        PermissionHelp.sendBluetoothScan {
            BluetoothSearch.search(null, null, null, searchFilter.timelong)
        }
    }

    /**
     * 自定义搜索蓝牙
     */
    fun search(filter: BluetoothSearchFilter) {
        if (isBlueEnable()) {
            PermissionHelp.sendBluetoothScan {
                BluetoothSearch.search(filter.UUIDs, filter.deviceName, filter.deviceMac, filter.timelong)
            }
        } else {
            enableBluetooth()
        }
    }

    /**
     * 暂停搜索蓝牙
     */
    fun stopSearch() {
        BluetoothSearch.stopSearch()
    }

    /**
     * 新增搜索监听
     */
    fun addSearchCallback(callback: BluetoothSeachCallback) {
        if (!searchs.contains(callback)) {
            searchs.add(callback)
        }
    }

    /**
     * 移除搜索监听
     */
    fun removeSearchCallback(callback: BluetoothSeachCallback) {
        if (searchs.contains(callback)) {
            searchs.remove(callback)
        }
    }


    /**
     * 开启前台服务
     */
    private var service: Intent? = null
    fun startService(context: Context): Boolean {
        if (!NotificationUtils.areNotificationsEnabled()) {
            LogUtils.e("请在设置界面开启通知，或者将有可能在息屏状态下被系统杀死APP的可能")
            return false
        }
        service = Intent(context, BluetoothService::class.java)

        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {
            PermissionHelp.sendTiramisuNotific {
                context.startForegroundService(service);
            }
        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                context.startForegroundService(service);
            } else {
                context.startService(service);
            }
        }
        return true
    }

    /**
     * 关闭前台服务
     */
    fun unregisService(context: Context) {
        service?.run {
            context.stopService(this)
        }
    }


    /**
     *  动态注册蓝牙广播
     */
    fun registerReceiver(context: Context) {
        val intent = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
        context.registerReceiver(receiver, intent)
    }

    /**
     * 动态注销蓝牙广播
     */
    fun unregisterReceiver(context: Context) {
        context.unregisterReceiver(receiver)
    }

    /**
     * 新增广播监听
     */
    fun addReceiverCallBack(callBack: ReceiverCallBack) {
        if (!receivers.contains(callBack)) {
            receivers.add(callBack)
        }
    }

    /**
     * 删除广播监听
     */
    fun removeReceiverCallBack(callBack: ReceiverCallBack) {
        if (receivers.contains(callBack)) {
            receivers.remove(callBack)
        }
    }

    /**
     * 打开蓝牙
     */
    fun enableBluetooth() {
        PermissionHelp.sendBluetoothConnect {
            if (this::bluetoothAdapter.isInitialized) {
                context.startActivity(Intent(Settings.ACTION_BLUETOOTH_SETTINGS).apply {
                    flags = Intent.FLAG_ACTIVITY_NEW_TASK
                });
            }
        }
    }

    /**
     * 关闭蓝牙
     */
    fun disableBluetooth() {
        PermissionHelp.sendBluetoothConnect {
            if (this::bluetoothAdapter.isInitialized) {
                if (bluetoothAdapter.isEnabled) {
                    context.startActivity(Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
                }
            }
        }
    }

    /**
     * 获取设备管理器
     */
    fun getConnectBleBluetooth(deviceData: BluetoothDeviceData): BleBluetooth? {
        val filter = connectDevices.keys.filter { deviceData.address == it }
        return if (filter.isNotEmpty()) {
            connectDevices[deviceData.address]!!.connect
        } else {
            null
        }
    }


    fun removeConnectGattCallback(bleDevice: BluetoothDeviceData) {
        getConnectBleBluetooth(bleDevice)?.run {
            removeConnectGattCallback()
        }
    }

    fun removeRssiCallback(bleDevice: BluetoothDeviceData) {
        getConnectBleBluetooth(bleDevice)?.run {
            removeRssiCallback()
        }
    }

    fun removeMtuChangedCallback(bleDevice: BluetoothDeviceData) {
        getConnectBleBluetooth(bleDevice)?.run {
            removeMtuChangedCallback()
        }
    }

    fun removeNotifyCallback(bleDevice: BluetoothDeviceData, uuid_notify: String) {
        getConnectBleBluetooth(bleDevice)?.run {
            removeNotifyCallback(uuid_notify)
        }
    }

    fun removeIndicateCallback(bleDevice: BluetoothDeviceData, uuid_indicate: String) {
        getConnectBleBluetooth(bleDevice)?.run {
            removeIndicateCallback(uuid_indicate)
        }
    }

    fun removeWriteCallback(bleDevice: BluetoothDeviceData, uuid_write: String) {
        getConnectBleBluetooth(bleDevice)?.run {
            removeWriteCallback(uuid_write)
        }
    }

    fun removeReadCallback(bleDevice: BluetoothDeviceData, uuid_read: String) {
        getConnectBleBluetooth(bleDevice)?.run {
            removeReadCallback(uuid_read)
        }
    }

    fun clearCharacterCallback(bleDevice: BluetoothDeviceData) {
        getConnectBleBluetooth(bleDevice)?.run {
            clearCharacterCallback()
        }
    }


    /**
     * 释放资源
     */
    fun destroy() {
        connectDevices.keys.forEachIndexed { _, key ->
            connectDevices[key]?.connect?.destroy()
        }
        connectDevices.clear()
    }

    /**
     * 蓝牙是否开启
     *
     */
    fun isBlueEnable(): Boolean {
        return this::bluetoothAdapter.isInitialized && bluetoothAdapter.isEnabled
    }
}